(function(angular) {
  'use strict';
  angular.module('fs.format').factory('formatService', [
    '$filter',
    'storageService',
    'SYS_PARAMS',
    function($filter, storageService, SYS_PARAMS) {
      var defaultFormat = {
        number: '#0.0##############',
        money: '#,##0.00#',
        percentage: '#0.0#'
      };
      function getMaximumIntegerDigits(pattern) {
        if (!pattern) return 1;
        if (pattern.indexOf('#') == 0) {
          return 2147483647;
        }
        pattern.replace('[,]', '');
        return pattern.length;
      }
      function getMaximumFractionDigits(pattern) {
        if (!pattern) return 0;
        return pattern.length;
      }
      function round(number, decimal) {
        if (isNaN(number)) return '';
        if (decimal < 0) return new Big(number).toString();
        return new Big(number).round(decimal).toString();
      }

      function formatNumber(number, pattern) {
        var maximumIntegerDigits, maximumFractionDigits, formatInfo, formatOptions, numberArray, numberString;
        var formatArray,
          formatString,
          length,
          len,
          i,
          result = '',
          sign = '';
        if (number === null || angular.isUndefined(number) || (angular.isString(number) ? number.trim() == '' : false)) {
          return;
        }
        formatInfo = storageService.getFormatInfo() || {};
        pattern = formatInfo[pattern] || pattern;
        pattern = pattern || formatInfo.defaultFloat || defaultFormat.number;
        if (!/.*[#0]+.*/.test(pattern.replace(/[^#0,\.]*/, ''))) {
          return number;
        }

        formatOptions = {
          decimalSeparator: formatInfo.decimalSeparator || '.',
          groupSeparator: formatInfo.groupSeparator || ',',
          groupingUsed: pattern.indexOf(',') >= 0,
          groupingSize: (pattern.indexOf('.') || pattern.length - 1) - pattern.indexOf(',') - 1
        };
        formatArray = pattern.split('.');
        maximumIntegerDigits = getMaximumIntegerDigits(formatArray[0]);
        maximumFractionDigits = getMaximumFractionDigits(formatArray.length > 1 ? formatArray[1] : '');
        if (isNaN(number)) return '';
        number = round(number, maximumFractionDigits);
        numberString = '' + number;
        numberArray = numberString.split('.');
        if (numberArray[0].length > maximumIntegerDigits) {
          numberArray[0] = numberArray[0].substr(numberArray[0].length - maximumIntegerDigits);
          numberString = numberArray.join('.');
        }

        numberArray = numberString.split('.');
        numberString = numberArray[0];
        formatString = formatArray[0];
        len = numberArray[0].length - 1;
        for (i = formatString.length - 1; i >= 0; i--) {
          switch (formatString.substr(i, 1)) {
            case '#':
              if (len >= 0) result = numberString.substr(len--, 1) + result;
              break;
            case '0':
              if (len >= 0) result = numberString.substr(len--, 1) + result;
              else result = '0' + result;
              break;
            case ',':
              result = formatOptions.groupSeparator + result;
              break;
          }
        }
        if (len >= 0) {
          if (formatOptions.groupingUsed) {
            length = numberArray[0].length;
            for (; len >= 0; len--) {
              result = numberArray[0].substr(len, 1) + result;
              if (len > 0 && (length - len) % formatOptions.groupingSize == 0) result = formatOptions.groupSeparator + result;
            }
          } else result = numberString.substr(0, len + 1) + result;
        }
        result += formatOptions.decimalSeparator;
        numberString = numberArray.length > 1 ? numberArray[1] : '';
        formatString = formatArray.length > 1 ? formatArray[1] : '';
        len = 0;
        for (var i = 0; i < formatString.length; i++) {
          switch (formatString.substr(i, 1)) {
            case '#':
              if (len < numberString.length) result += numberString.substr(len++, 1);
              break;
            case '0':
              if (len < numberString.length) result += numberString.substr(len++, 1);
              else result += '0';
              break;
          }
        }
        if (result.charAt(0) == '-' || result.charAt(0) == '+') {
          sign = result.charAt(0);
          result = result.substring(1);
        }
        result = sign + result.replace(/^\,|^\./, '').replace(/\.$|\,$/, '');
        return result;
      }
      function formatMoney(money, pattern) {
        // pattern is ccy or ccy format
        var formatInfo,
          siteInfo,
          ccyConfig = SYS_PARAMS.CCY_CONFIG,
          zeros = '.000000000000000000000000000000000000000000000000',
          fractionDigits;
        formatInfo = storageService.getFormatInfo() || {};
        siteInfo = storageService.getSiteInfo() || {};
        if (ccyConfig[angular.uppercase(pattern)] == undefined) {
          fractionDigits = ccyConfig[siteInfo.baseCcy];
        } else {
          fractionDigits = ccyConfig[angular.uppercase(pattern)];
        }
        if (!(money === null || angular.isUndefined(money) || (angular.isString(money) ? money.trim() == '' : false))) {
          pattern = (formatInfo.money0 || '#,##0') + zeros.substr(0, fractionDigits + 1);
        } else {
          pattern = formatInfo[pattern];
          pattern = pattern || formatInfo['defaultMoney'];
        }
        return formatNumber(money, pattern);
      }
      function formatSst(sensitiveInfo, pattern) {
        return $filter('sensitive')(sensitiveInfo, pattern.positiveOffset, pattern.negativeOffset, pattern.mask);
      }
      function formatPercentage(number, pattern) {
        var formatInfo = storageService.getFormatInfo() || {};
        pattern = formatInfo[pattern];
        pattern = pattern || defaultFormat.percentage;
        number = formatNumber(number * 100, pattern);
        return (number = number ? number + '%' : number);
      }
      function formatDate(date, pattern) {
        var formatInfo = storageService.getFormatInfo() || {};
        pattern = formatInfo[pattern] || pattern;
        return $filter('dateFormat')(date, pattern);
      }
      function parseNumber(number) {
        var formatInfo, decimalSeparator, groupSeparator;
        if (angular.isNumber(number)) return number;
        if (!angular.isString(number)) {
          return number;
        }
        if (number === null || angular.isUndefined(number) || (angular.isString(number) ? number.trim() == '' : false)) {
          return '';
        }
        formatInfo = storageService.getFormatInfo() || {};
        decimalSeparator = formatInfo.decimalSeparator || '.';
        groupSeparator = formatInfo.groupSeparator || ',';
        number = number.replace(eval('/\\' + groupSeparator + '/g'), '');
        number = number.replace(eval('/\\' + decimalSeparator + '/g'), '.');
        return isNaN(number) ? '' : new Big(number).toString();
      }
      function parseMoney(money) {
        return parseNumber(money);
      }
      function parsePercentage(percentage) {
        if (!percentage || percentage == '') {
          return percentage;
        }
        if (angular.isNumber(percentage)) return percentage / 100;
        return parseNumber(percentage.replace(/%$/g, '')) / 100;
      }
      function parseDate(date, pattern) {
        var formatInfo,
          i_val = 0,
          i_format = 0,
          c = '',
          token = '',
          token2 = '',
          x,
          y,
          year = new Date().getFullYear();
        var month = 1,
          day = 1,
          hh = 0,
          mm = 0,
          ss = 0,
          sss = 0,
          ampm = '',
          zs = '',
          zh = 0,
          zm = 0;
        if (!date || !pattern) return date;
        formatInfo = storageService.getFormatInfo() || {};
        pattern = formatInfo[pattern] || pattern;
        // check the value is 0 - 9.
        this.isInteger = function(val) {
          for (var i = 0; i < val.length; i++) {
            if ('1234567890'.indexOf(val.charAt(i)) == -1) {
              return false;
            }
          }
          return true;
        };
        // Gets the token from minlength to maxlength.
        this.getInt = function(str, i, minlength, maxlength) {
          for (var x = maxlength; x >= minlength; x--) {
            var token = str.substring(i, i + x);
            if (token.length < minlength) {
              return null;
            }
            if (this.isInteger(token)) {
              return token;
            }
          }
          return null;
        };

        this.buildDate = function(year, month, day, hh, mm, ss, sss, zh, zm, zs) {
          var date = new Date(0);
          var dateSetter = date.setFullYear; //zs != '' ? date.setUTCFullYear : date.setFullYear;
          var timeSetter = date.setHours; //zs != '' ? date.setUTCHours : date.setHours;
          var h = hh || 0; //- zh;
          var m = mm || 0; //- zm;
          var s = ss || 0;
          var ms = Math.round(parseFloat('0.' + (sss || 0)) * 1000);
          dateSetter.call(date, year, month - 1, day);
          timeSetter.call(date, h, m, s, ms);
          return date;
        };

        while (i_format < pattern.length) {
          c = pattern.charAt(i_format);
          token = '';
          while (pattern.charAt(i_format) == c && i_format < pattern.length) {
            token += pattern.charAt(i_format++);
          }
          if (token == 'yyyy' || token == 'yy' || token == 'y') {
            if (token == 'yyyy') {
              x = 4;
              y = 4;
            }
            if (token == 'yy') {
              x = 2;
              y = 2;
            }
            if (token == 'y') {
              x = 2;
              y = 4;
            }
            year = this.getInt(date, i_val, x, y);
            if (year == null) return date;
            i_val += year.length;
          } else if (token == 'MM' || token == 'M') {
            month = this.getInt(date, i_val, token.length, 2);
            if (month == null || month < 1 || month > 12) return date;
            i_val += month.length;
          } else if (token == 'dd' || token == 'd') {
            day = this.getInt(date, i_val, token.length, 2);
            if (day == null || day < 1 || day > 31) return date;
            i_val += day.length;
          } else if (token == 'hh' || token == 'h') {
            hh = this.getInt(date, i_val, token.length, 2);
            if (hh == null || hh < 1 || hh > 12) return date;
            i_val += hh.length;
          } else if (token == 'HH' || token == 'H') {
            hh = this.getInt(date, i_val, token.length, 2);
            if (hh == null || hh < 0 || hh > 23) return date;
            i_val += hh.length;
          } else if (token == 'mm' || token == 'm') {
            mm = this.getInt(date, i_val, token.length, 2);
            if (mm == null || mm < 0 || mm > 59) return null;
            i_val += mm.length;
          } else if (token == 'ss' || token == 's') {
            ss = this.getInt(date, i_val, token.length, 2);
            if (ss == null || ss < 0 || ss > 59) return date;
            i_val += ss.length;
          } else if (token == 'sss' || token == 'SSS' || token == 'S') {
            sss = this.getInt(date, i_val, token.length, 3);
            if (sss == null || sss < 0 || sss > 999) return date;
            i_val += sss.length;
          } else if (token == 'Z' || token == 'z') {
            zs = date.substring(i_val, i_val + 1);
            zh = this.getInt(date, i_val + 1, 2, 2);
            zm = this.getInt(date, i_val + 3, 2, 2);
            zh = zs == '+' ? zh : -zh;
            zm = zs == '+' ? zm : -zm;
            i_val += 5;
          } else if (token == 'a') {
            if (date.substring(i_val, i_val + 2).toLowerCase() == 'am') {
              ampm = 'AM';
            } else if (date.substring(i_val, i_val + 2).toLowerCase() == 'pm') {
              ampm = 'PM';
            } else {
              return date;
            }
            i_val += 2;
          } else {
            if (date.substring(i_val, i_val + token.length) != token) {
              return date;
            } else {
              i_val += token.length;
            }
          }
        }
        // validate data
        if (i_val != date.length) return date;
        // validate month
        if (month == 2) {
          if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) {
            if (day > 29) return date;
          } else {
            if (day > 28) return date;
          }
        }
        if (month == 4 || month == 6 || month == 9 || month == 11) {
          if (day > 30) return date;
        }
        // set ampm
        if (hh < 12 && ampm == 'PM') {
          hh = hh - 0 + 12;
        } else if (hh > 11 && ampm == 'AM') {
          hh -= 12;
        }
        return this.buildDate(year, month, day, hh, mm, ss, sss, zh, zm, zs);
      }
      return {
        formatNumber: formatNumber,
        formatMoney: formatMoney,
        formatPercentage: formatPercentage,
        formatDate: formatDate,
        formatSst: formatSst,
        parseNumber: parseNumber,
        parseMoney: parseMoney,
        parsePercentage: parsePercentage,
        parseDate: parseDate,
        round: round
      };
    }
  ]);
})(angular);
